package gecko;

import gecko.lang.TypedMap;
import gecko.x.CastX;
import gecko.x.ClassX;
import gecko.x.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author 陈永佳 (yoojiachen@gmail.com)
 * @version 0.0.1
 */
public class GeckoScoped {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeckoContext.class);

    private ExecutorService mThreads = Executors.newCachedThreadPool();

    private TypedMap mGecko = new TypedMap();
    private TypedMap mGlobals = new TypedMap();
    private Map<Type, Object> mMagicBox = new HashMap<>();

    TypedMap RouterConfig = new TypedMap();
    Map<String, TypedMap> InterceptorConfigs = new HashMap<>();
    Map<String, TypedMap> DriversConfigs = new HashMap<>();
    Map<String, TypedMap> DevicesConfigs = new HashMap<>();
    Map<String, TypedMap> TriggersConfigs = new HashMap<>();
    Map<String, TypedMap> PluginsConfigs = new HashMap<>();

    /**
     * 初始化Context
     * 此方法属于框架内部函数，不对外使用。
     *
     * @param appConfig 全局配置对象
     */
    final void init(TypedMap appConfig) {
        this.mGecko = appConfig.getDictMap("GECKO");
        this.mGlobals = appConfig.getDictMap("GLOBALS");
        this.RouterConfig = appConfig.getDictMap("ROUTERS");
        this.InterceptorConfigs = map2conf(appConfig.getStrMap("INTERCEPTORS"));
        this.DriversConfigs = map2conf(appConfig.getStrMap("DRIVERS"));
        this.DevicesConfigs = map2conf(appConfig.getStrMap("DEVICES"));
        this.TriggersConfigs = map2conf(appConfig.getStrMap("TRIGGERS"));
        this.PluginsConfigs = map2conf(appConfig.getStrMap("TRIGGERS"));
        // check args
        if (Strings.isNullOrEmpty(getDomain())) {
            throw new IllegalArgumentException("GECKO.domain 是必须配置的参数");
        }
        if (Strings.isNullOrEmpty(getNodeId())) {
            throw new IllegalArgumentException("GECKO.nodeId 是必须配置的参数");
        }
    }

    /**
     * 释放占用资源
     */
    final void release() {
        mThreads.shutdown();
    }

    /**
     * @return 返回Domain
     */
    final public String getDomain() {
        return mGecko.getString("domain");
    }

    /**
     * @return 返回设备节点ID
     */
    final public String getNodeId() {
        return mGecko.getString("nodeId");
    }

    /**
     * 获取 GLOBALS 配置对象
     *
     * @return DictMap对象，非null
     */
    final public TypedMap GLOBALS() {
        return mGlobals;
    }

    /**
     * 获取版本号信息
     *
     * @return Version
     */
    final public String version() {
        return mGlobals.getString("version", "G1");
    }

    public void magic(Type type, Object value) {
        mMagicBox.put(type, value);
    }

    @SuppressWarnings("unchecked")
    public <T extends Type> T magic(Type type) {
        return (T) mMagicBox.get(type);
    }

    ////////////////////

    /**
     * 此方法属于框架内部函数，不对外使用。
     */
    final long __dataCenterId() {
        return mGecko.getLong("dataCenterId");
    }

    /**
     * 此方法属于框架内部函数，不对外使用。
     */
    final long __workerId() {
        return mGecko.getLong("workerId");
    }

    ///////////////////

    private Map<String, TypedMap> map2conf(Map<String, Object> data) {
        return data.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        kv -> TypedMap.wrap(CastX.toStrMap(kv.getValue()))
                ));
    }

    /**
     * 检查一个操作是否运行时间过长。如果操作执行时间超过指定时长，会在日志中输出报告。
     *
     * @param actionName 动作名称
     * @param timeoutMS  超时
     * @param operation  操作
     */
    final public void detectTimeout(String actionName, long timeoutMS, Runnable operation) {
        final Timer timer = new Timer(true);
        try {
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    LOGGER.warn("{} 运行时间过长，超过{}ms", actionName, timeoutMS);
                }
            }, timeoutMS);
            operation.run();
        } finally {
            timer.cancel();
        }
    }

    /**
     * 返回是否启用了详细输出日志的标记位。
     * 详细输出标记位，由配置文件的 GLOBALS.loggingVerbose 配置参数控制。
     *
     * @return 返回是否启用了详细输出日志的标记位
     */
    public boolean isLogVerboseEnabled() {
        return mGlobals.getBoolean("loggingVerbose");
    }

    /**
     * 当Globals启用了详细输出标志位时才打印Debug日志信息
     *
     * @param format Message format
     * @param args   Args
     */
    public void DebugIfV(String format, Object... args) {
        DebugIfV(LOGGER, format, args);
    }

    /**
     * 当Globals启用了详细输出标志位时才打印Debug日志信息
     *
     * @param logger Logger
     * @param format Message format
     * @param args   Args
     */
    public void DebugIfV(Logger logger, String format, Object... args) {
        LogIfV(() -> logger.debug(format, args));
    }

    /**
     * 当Globals启用了详细输出标志位时才执行函数
     *
     * @param func Args
     */
    public void LogIfV(Runnable func) {
        if (isLogVerboseEnabled()) {
            func.run();
        }
    }

    /**
     * 提交一个异步任务到线程池中执行，返回Future
     *
     * @param task Runnable
     * @return Future
     */
    public Future<?> submit(Runnable task) {
        return mThreads.submit(task);
    }

    /**
     * 提交一个异步任务到线程池中执行，返回Future
     *
     * @param supplier Supplier
     * @param <T>      Type of returns
     * @return Future
     */
    public <T> CompletableFuture<T> submitFuture(Supplier<T> supplier) {
        return CompletableFuture.supplyAsync(supplier, mThreads);
    }

    ////

    final void startTakes(Bundle bundle) {
        takes(ClassX.nameOf(bundle) + ".Start", () -> bundle.onStart(this));
    }

    final void stopTakes(Bundle bundle) {
        takes(ClassX.nameOf(bundle) + ".Stop", () -> bundle.onStop(this));
    }

    final void takes(String name, Runnable task) {
        final long start = System.nanoTime();
        try {
            task.run();
        } finally {
            LOGGER.debug("{} takes: {}ms", name,
                    TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS));
        }
    }

}
